package com.github.smartheye.fabric.common.crypto;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

public class SHA256 {

    public static byte[] getSalt() throws NoSuchAlgorithmException {
        // Always use a SecureRandom generator
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        // Create array for salt
        byte[] salt = new byte[16];
        // Get a random salt
        sr.nextBytes(salt);
        // return salt
        return salt;
    }

    public static byte[] hash(String str, byte[] salt) throws NoSuchAlgorithmException, NoSuchProviderException {
        byte[] src = null;
        try {
            src = str.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            // unhandle it because UTF-8 always exists
            e.printStackTrace();
        }
        return hash(src, salt);
    }

    public static byte[] hash(byte[] src) {
        return hash(src, null);
    }

    public static byte[] hash(byte[] src, byte[] salt) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("SHA256", SecurityManager.getProvider());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        if (salt != null && salt.length > 0) {
            md.update(salt);
        }
        md.update(src);
        return md.digest();
    }

    /**
     * Calculates the SHA-256 hash of the given bytes,
     * and then hashes the resulting hash again.
     *
     * @param input the bytes to hash
     * @return the double-hash (in big-endian order)
     */
    public static byte[] hashTwice(byte[] input) {
        return hashTwice(input, 0, input.length);
    }

    /**
     * Calculates the SHA-256 hash of the given byte range,
     * and then hashes the resulting hash again.
     *
     * @param input the array containing the bytes to hash
     * @param offset the offset within the array of the bytes to hash
     * @param length the number of bytes to hash
     * @return the double-hash (in big-endian order)
     */
    public static byte[] hashTwice(byte[] input, int offset, int length) {
        MessageDigest digest = newDigest();
        digest.update(input, offset, length);
        return digest.digest(digest.digest());
    }

    /**
     * Calculates the hash of hash on the given byte ranges. This is equivalent to
     * concatenating the two ranges and then passing the result to {@link #hashTwice(byte[])}.
     */
    public static byte[] hashTwice(byte[] input1, int offset1, int length1, byte[] input2, int offset2, int length2) {
        MessageDigest digest = newDigest();
        digest.update(input1, offset1, length1);
        digest.update(input2, offset2, length2);
        return digest.digest(digest.digest());
    }

    private static MessageDigest newDigest() {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("SHA256", SecurityManager.getProvider());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return md;
    }
}
